#ifndef FILE_OP_HPP
#define	FILE_OP_HPP

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include "logger.hpp"

#define ITERATOR_STEP 1000
#define NUM_LEN 100
class MapIterator;

std::string itostr(int a){
    std::string str;
    std::stringstream stream(str);
    stream << " " << a << " ";
    return stream.str();
}

/*
 * This function takes an array of symbols, interpreted as a sequence of comma-separated integers,
 * and it's length. In case of success a vector of integers is returned. If something goes wrong,
 * an empty vector is returned. To validate an input, string::find_last_not_of() function is used.
*/
std::vector<int> parse(const char* arr, 
                       int length,
                       std::pair<std::string,std::string> &res){
        log(INF,"parse: start");
	std::vector<int> output;
        std::string poss_chars("1234567890,-");
        poss_chars += '\n';
        
	if(NULL == arr || 0 >= length){
            log(ERR,"parse:incorrect or missing input: missing array or length is zero");
            return output;
        }
	std::string inp_str(arr,length);
        size_t err_info = 0;
        err_info = inp_str.find_last_not_of(poss_chars);
	if(err_info != std::string::npos){
            log(ERR,"parse:incorrect or missing input: wrong characters in input");
            return output;
        }
        const char *inp = NULL;
        char buffer[NUM_LEN] = {0};
        inp = inp_str.c_str();
        unsigned int start_loc = 0;
        unsigned int i = 0;
        unsigned int k = 0;
        for(i = 0; i < length; i++)
            if(',' == inp[i])
                break;
        if(i == length)
            return output;
        start_loc = i+1;
        res.first = std::string(inp,i);
        
        for(i = length -1 ; i >= 0; i--)
            if(',' == inp[i])
                break;
        length = i;
        res.second = std::string(inp+i+1);
        
        while(start_loc < length){
            for(i = start_loc; i < length; i++){
                buffer[k] = inp[i];
                if(',' == inp[i])
                    break;
                k++;
            }
            buffer[k] = 0;
            output.push_back(atoi(buffer));
            start_loc += k+1;
            k = 0;
            memset(buffer,0,NUM_LEN);
            
        }
        log(INF,"parse:success");
	return output;
}

void rev_parse(const std::vector<int> &v,char *str){
    log(INF,"rev_parse: start");
    int symbols = 0;
    for(unsigned int i = 0; i < v.size(); i++)
        symbols += sprintf(str+symbols,"%d,",v[i]);
    log(INF,"rev_parse: success");
}


class File{
protected:
    int file_desc;
    int file_size;
    char *mapping;
    int map_size;
    int mode;
public:
    File(const char *name,int mode);
    int get_file_desc();
    int get_mode();    
    int get_file_size();
    int get_map_size();
    char* get_map_ptr();
    void calc_size();
    bool is_good();
    char* map_file(int needed_size = 0);
    MapIterator& begin();
    MapIterator& end();
    ~File();
};
class MapIterator{
    MapIterator(const MapIterator &old){};
    File *base;
    char *data;
    int length;
    int start_pos;
    int end_pos;
    bool synchronized;
public:
    static long int iterator_size;
    friend MapIterator& File::begin();
    friend MapIterator& File::end();
    MapIterator();
    MapIterator& operator++();
    MapIterator& operator=(MapIterator& right);
    bool operator!=(const MapIterator& right) const;
    char* operator*();
    bool map_part();
    void sync();
    void write(const char *str,int len);
    int get_length() const;
    void print_pos();
    bool is_good();
    ~MapIterator();
};
long int MapIterator::iterator_size = sysconf(_SC_PAGE_SIZE)*ITERATOR_STEP;

File::File(const char *name,int mode){
    mapping = NULL;
    file_size = 0;
    map_size = 0;
    this->mode = (mode & O_RDONLY) +  (mode & O_WRONLY) + (mode & O_RDWR);
    if(mode == O_WRONLY || mode == O_RDWR)
        file_desc = open(name,mode|O_CREAT|O_TRUNC,0666);
    else
        file_desc = open(name,mode);
    log(INF,std::string("File::File:open file_descriptor")
            += itostr(file_desc)
            += sys_errlist[errno]);
    file_size = lseek(file_desc,0,SEEK_END);
    lseek(file_desc,0,SEEK_SET);
    if(file_size <= 0){
        log(WARN,std::string(name) += " is empty");
    }
}

File::~File(){
    close(file_desc);
    log(INF,std::string("File descriptor") += itostr(file_desc) += "was closed");
    if(mapping)
        munmap(mapping,map_size);
    log(INF,std::string("File descriptor") 
            += itostr(file_desc) 
            += std::string("unmap file ") 
            += sys_errlist[errno]);
}
int File::get_file_desc(){
    return file_desc;
}
int File::get_mode(){
    return mode;
}
int File::get_file_size(){
    if(-1 == file_desc)
        return -1;
    return file_size;
}
int File::get_map_size(){
    if(!mapping)
        return -1;
    return map_size;
}
char* File::get_map_ptr(){
    return mapping;
}
void File::calc_size(){
    file_size = lseek(file_desc,0,SEEK_END);
    lseek(file_desc,0,SEEK_SET);
}
bool File::is_good(){
    if(file_desc < 0)
        return false;
    if(file_size < 0)
        return false;
    return true;
}

char* File::map_file(int needed_size){
    if(!is_good()){
        log(ERR,"File::map_file:bad output file");
        return NULL;
    }
    if(0 == needed_size){
        calc_size();
        needed_size = get_file_size();
    }
    if(O_WRONLY == mode || O_RDWR == mode)
        if(file_size <= needed_size){
            file_size = ftruncate(file_desc, needed_size);
            if(file_size == -1){
                    log(ERR,
                        std::string("File::map_file: ftruncate - ") 
                            += sys_errlist[errno]);
                    return NULL;
            }
            log(INF,"File::map_file:output file was truncated");  
            file_size = needed_size;

        }
    if(O_RDONLY == mode)
        mapping = (char*)mmap(NULL,needed_size,PROT_READ,MAP_PRIVATE,file_desc,0);
    else if(O_WRONLY == mode)
        mapping = (char*)mmap(NULL,needed_size,PROT_WRITE,MAP_SHARED,file_desc,0);
    else
        mapping = (char*)mmap(NULL,needed_size,PROT_READ|PROT_WRITE,MAP_SHARED,file_desc,0);
    map_size = needed_size;
    if(MAP_FAILED == mapping){
                log(ERR,std::string("OutFile::map_file:unable to map output file ") 
                        += sys_errlist[errno]);
                map_size = 0;
                mapping = NULL;
    }
    if(!mapping)
        log(ERR,"OutFile::map_file:output file mapping failed");
    log(INF,"OutFile::map_file:output file mapping - success");
    return mapping;
}

MapIterator& File::begin(){
    static MapIterator it_begin;
    if(!File::is_good()){
        log(ERR,"File::begin:bad file");
        return it_begin;
    }
    it_begin.base = this;
    it_begin.start_pos = 0;
    if(file_size > it_begin.iterator_size)
        it_begin.length = it_begin.iterator_size;
    else
        it_begin.length = file_size;
    it_begin.end_pos = it_begin.length;
    it_begin.map_part();
    if(it_begin.is_good())
        log(INF,"File::begin: begin iterator was created");
    else
        log(ERR,"File::begin: cannot create iterator");
    return it_begin;
}

MapIterator& File::end(){
    static MapIterator it_end;
    if(!File::is_good()){
        log(ERR,"File::end:bad file");
        return it_end;
    }
    it_end.base = this;
    if(get_file_size() < it_end.iterator_size)
        return begin();
    if( (get_file_size() % it_end.iterator_size) ){
        it_end.start_pos = it_end.iterator_size*(get_file_size()/it_end.iterator_size);
    }
    else{
        it_end.start_pos = it_end.iterator_size*(get_file_size()/it_end.iterator_size - 1);
    }
    it_end.length = file_size - it_end.start_pos;
    it_end.end_pos = file_size;
    it_end.data = NULL;
    it_end.map_part();
    if(it_end.is_good())
        log(INF,"File::end: end iterator was created");
    else
        log(ERR,"File::end: cannot create iterator");
    return it_end;
}


class InFile:public File{
public:
    InFile(const char *filename):File(filename,O_RDONLY){};
    bool can_read(int needed_size);
};


bool InFile::can_read(int needed_size){
    if(!is_good())
        return false;
    if(needed_size <= 0)
        return false;
    if(needed_size > file_size)
        return false;
    return true;
}

class OutFile:public File{
public:
    OutFile(const char *filename):File(filename,O_RDWR){};
    bool iter_write(std::vector<int> &out);
    bool write_file(std::vector<int> &v,int size);
    bool can_write();
};

bool OutFile::iter_write(std::vector<int> &out){
     std::string str;
     //rev_parse(out,str,0,out.size());
     if(str.size() <= 0){
         log(ERR,"reverse parsing failed");
         return false;
     }
     if( -1 == ftruncate(file_desc, str.size()) ){
         log(ERR,std::string("File::iter_write:unable to truncate file") += sys_errlist[errno]);
         return false;
     }
     out.clear();
     calc_size();
     MapIterator it1,it2;

     it1 = begin();
     it2 = end();
     if(!it2.is_good())
         return false;
     std::string substring;
     while(it1 != it2){
         if(!it1.is_good())
             return false;
         substring = str.substr(0,it1.get_length());
         it1.write( substring.c_str(), it1.get_length() );
         str.erase(0,it1.get_length());
         ++it1;
     }
     substring = str.substr(0,it1.get_length());
     it1.write( substring.c_str(), it1.get_length() );
     if(!it1.is_good())
         return false;
     return true;
 }

bool OutFile::write_file(std::vector<int> &v,int size){
    if(!can_write()){
        log(ERR,"OutFile::write_file:bad output file");
        return false;
    }
    if(v.size() == 0){
        log(WARN,"OutFile::write_file:nothing to write");
        return false;
    }
    if(size <= 0)
        return false;
    //log(INF,std::string("OutFile::write_file:String to write is ") += out_str);
    OutFile::map_file(size);
    if(!mapping){
        log(ERR,std::string("OutFile::write_file:map_file - ") += sys_errlist[errno]);
        return false;
    }
    log(INF,"OutFile::write_file:output file mapping - success");
    rev_parse(v,mapping);
    log(INF,"OutFile::write_file:writing to mapped memory - success");
    if(msync(mapping,get_map_size(),MS_SYNC) == -1){
        log(ERR,std::string("OutFile::write_file:msync - ") += sys_errlist[errno]);
        return false;
    }
    log(INF,"OutFile::write_file:msync - success");
    return true;
}

bool OutFile::can_write(){
    if(!is_good())
        return false;
    return true;
}
    



MapIterator::MapIterator(){
    base = NULL;
    data = NULL;
    length = 0;
    start_pos = 0;
    end_pos = 0;
    synchronized = true;
    log(INF,"empty iterator was created");
}

bool MapIterator::map_part(){
    if(base->get_mode() == O_RDONLY)
        data = (char*)mmap(NULL,length,
                        PROT_READ,MAP_SHARED,
                        base->get_file_desc(),start_pos);
    if(base->get_mode() == O_WRONLY)
        data = (char*)mmap(NULL,length,
                        PROT_WRITE,MAP_SHARED,
                        base->get_file_desc(),start_pos);
    if(base->get_mode() == O_RDWR)
        data = (char*)mmap(NULL,length,
                        PROT_WRITE|PROT_READ,MAP_SHARED,
                        base->get_file_desc(),start_pos);
    if(MAP_FAILED == data){
        data = NULL;
        log(ERR,std::string("MapIterator::map_part:mapping failed ") 
                += sys_errlist[errno]);
        return false;
    }
    log(INF,"MapIterator::map_part:success");
    return true;
}

MapIterator& MapIterator::operator=(MapIterator& right){
    base = right.base;
    right.base = NULL;
    
    data = right.data;
    right.data = NULL;
    
    length = right.length;
    start_pos = right.start_pos;
    end_pos = right.end_pos;
    right.length = right.start_pos = right.end_pos = 0;
    if(!is_good())
        log(ERR,"MapIterator::operator=: cannot assign iterator");
    else
        log(INF,"MapIterator::operator=:iterator was assigned");
    return *this;
    
}

MapIterator& MapIterator::operator++(){
    if(!is_good()){
        log(ERR,"MapIterator& MapIterator::operator++: bad iterator");
        return *this;
    }
    if(end_pos == base->get_file_size())
        return *this;
    if(base->get_file_size() - end_pos < iterator_size){
        *this = base->end();
        return *this;
    }
    sync();
    munmap(data,length);
    start_pos = end_pos;
    end_pos = start_pos + iterator_size;
    length = end_pos - start_pos;
    map_part();
    if(!is_good())
        log(ERR,"MapIterator::operator++: unable to ++ iterator");
    log(INF,"MapIterator& MapIterator::operator++: success");
    return *this;
}

bool MapIterator::operator!=(const MapIterator& right)const{
    if(this->base != right.base)
        return true;
    if(this->start_pos != right.start_pos)
        return true;
    if(this->end_pos != right.end_pos)
        return true;
    log(INF,"MapIterator::operator!=: iterators are equal");
    return false;
}

char* MapIterator::operator*(){
    return data;
}
void MapIterator::write(const char *str,int len){
    if(!is_good()){
        log(ERR,"MapIterator::write: bad iterator");
        return;
    }
    memcpy(data,str,len);
    sync();
    if(!is_good())
        log(ERR,"MapIterator::write:unable to write iterator data to file");
}
int MapIterator::get_length()const{
    return length;
}
void MapIterator::sync(){
    if(msync(data,get_length(),MS_SYNC) == -1){
        log(ERR,"MapIterator::sync:unable to write iterator data on disc");
        synchronized = false;
    }
    synchronized = true;
    log(INF,"MapIterator::sync:iterator data was written successfully");
    return;
}
bool MapIterator::is_good(){
    if( !base || !(base->is_good()) ){
        log(ERR,"MapIterator::is_good: base file is bad");
        return false;
    }
    if(!data){
        log(ERR,"MapIterator::is_good: iterator mapping is bad");
        return false;
    }
    if(length <= 0){
        log(ERR,"MapIterator::is_good: iterator length is less than zero");
        return false;
    }
    if(end_pos - start_pos != length){
        log(ERR,"MapIterator::is_good: iterator is inconsistent");
        return false;
    }
    if(!synchronized)
        return false;
    return true;
}

void MapIterator::print_pos(){
    std::cout << "start_pos = " << start_pos << std::endl;
    std::cout << "end_pos = " << end_pos << std::endl;
}

MapIterator::~MapIterator(){
    if(data)
        munmap(data,length);
    data = NULL;
    log(INF,"iterator was destroyed");
}
#endif	/* FILE_OP_HPP */

